import fs from "fs-extra";
import { TLangs } from "./Translator.js";
import { toolchain } from "./toolchain.js";
import { Ei18nErrorCode } from "./errors.js";
import { LocalizeMode } from "./LocalizeOption.js";

export const enum EFormatErr {
    RichFormatSlashPairErr = '富文本#配对错误',
    HtmlFormatErr = 'html格式错误',
    RichFormatErr = '富文本格式错误',
    EscapeErr = '转义符错误'
}

declare interface IFormatErr {
    local: string,
    errors: EFormatErr[]
}

declare interface IMissedFormats {
    local: string,
    fmts: string[]
}

declare interface IReport {
    noLocals: string[],
    missedFmts: IMissedFormats[],
    fmtErrors: IFormatErr[],
    termCNs: string[],
    termENs: string[]
}

declare interface IReportDoc {
    noTranslators: string[],
    concatStrErrors: string[],
    jsonSafeErrors: string[],
    langs: Record<string, IReport>
}

export class Reporter {
    private noTranslators: string[] = [];
    private concatStrErrors: string[] = [];
    private jsonSafeErrors: string[] = [];

    private readonly langMap: Record<string, IReport> = {};

    public addNoLocal(zh: string, langs: TLangs[]): void {
        for (const lang of langs) {
            const report = this.touchReport(lang);
            if (!report.noLocals.includes(zh)) {
                report.noLocals.push(zh);
            }
        }
    }

    public addNoTranslator(file: string): void {
        if (!this.noTranslators.includes(file)) {
            this.noTranslators.push(file);
        }
    }

    public addConcatStrError(err: string): void {
        this.concatStrErrors.push(err);
    }

    public addJsonSafeError(err: string): void {
        if (!this.jsonSafeErrors.includes(err)) {
            this.jsonSafeErrors.push(err);
        }
    }

    public addMissedFormats(local: string, lang: TLangs, fmt: string): void {
        const report = this.touchReport(lang);
        let mf = report.missedFmts.find((v) => v.local == local);
        if (mf == null) {
            mf = { local, fmts: [] };
            report.missedFmts.push(mf);
        }
        if (!mf.fmts.includes(fmt)) mf.fmts.push(fmt);
    }

    public addFormatError(local: string, lang: TLangs, error: EFormatErr): void {
        const report = this.touchReport(lang);
        let fe = report.fmtErrors.find((v) => v.local == local);
        if (fe == null) {
            fe = { local, errors: [] };
            report.fmtErrors.push(fe);
        }
        if (!fe.errors.includes(error)) {
            fe.errors.push(error);
        }
    }

    public addTermCNError(local: string, lang: TLangs): void {
        this.touchReport(lang).termCNs.push(local);
    }

    public addTermENError(local: string, lang: TLangs): void {
        this.touchReport(lang).termENs.push(local);
    }

    public async checkErrors(): Promise<void> {
        let errorCode = 0;

        for (const lang in this.langMap) {
            const report = this.langMap[lang];
            if (report.missedFmts.length > 0 || report.fmtErrors.length > 0 || report.termCNs.length > 0 || report.termENs.length > 0) {
                errorCode |= Ei18nErrorCode.FormatError;
                console.log('######################    ' + lang + '    ######################');
                if (report.missedFmts.length > 0) {
                    const fms: string[] = [];
                    for (const mf of report.missedFmts) {
                        fms.push('<' + mf.fmts.join(',') + '>    ' + mf.local);
                    }
                    this.printMultiLines(`Format missing (${fms.length})`, fms);
                }
                if (report.fmtErrors.length > 0) {
                    this.printMultiLines(`Format error (${report.fmtErrors.length})`, report.fmtErrors.map((v) => `<${v.errors.join(',')}>    ` + v.local));
                }
                if (report.termCNs.length > 0) {
                    this.printMultiLines(`TermCN error (${report.termCNs.length})`, report.termCNs);
                }
                if (report.termENs.length > 0) {
                    this.printMultiLines(`TermEN error (${report.termENs.length})`, report.termENs);
                }
                console.log('');
            }
        }

        if (!toolchain.globalOption.ignoreErrors && errorCode != 0) {
            await this.writeReportFile();
            console.log('exit code:', errorCode);
            process.exit(errorCode);
        }
    }

    public async makeSumary(mode: LocalizeMode, blackMap: { [cn: string]: true }): Promise<void> {
        let errorCode = 0;

        const noLocalMap: Record<string, TLangs[]> = {};
        for (const lang in this.langMap) {
            const report = this.langMap[lang];
            for (const zh of report.noLocals) {
                let arr = noLocalMap[zh];
                if (!arr) noLocalMap[zh] = arr = [];
                if (!arr.includes(<TLangs>lang)) arr.push(lang as TLangs);
            }
        }

        if (Object.keys(noLocalMap).length > 0) {
            const arr: string[] = [];
            for (const zh in noLocalMap) {
                if (!blackMap[zh]) {
                    arr.push(zh + '    ... ' + noLocalMap[zh].join(', '));
                }
            }
            if (arr.length > 0 && toolchain.globalOption.strict) {
                console.error('[unity-i18n]No local:', arr.length);
                this.printMultiLines('No local', arr);

                // 搜索时不以NoLocal为错误
                if (mode == LocalizeMode.Replace) errorCode |= Ei18nErrorCode.NoLocal;
            }
        }

        if (mode == LocalizeMode.Replace) {
            // 检查任务错误
            if (this.concatStrErrors.length > 0) {
                console.error('[unity-i18n]Concat error:', this.concatStrErrors.length);
                this.printMultiLines('Concat error', this.concatStrErrors);
                errorCode |= Ei18nErrorCode.ConcatStrings;
            }

            if (this.jsonSafeErrors.length > 0) {
                console.error('[unity-i18n]JSON error:', this.jsonSafeErrors.length);
                this.printMultiLines('JSON error', this.jsonSafeErrors);
                errorCode |= Ei18nErrorCode.SyntaxError;
            }
        }

        await this.writeReportFile();

        if (!toolchain.globalOption.ignoreErrors && errorCode != 0) {
            console.log('exit code:', errorCode);
            process.exit(errorCode);
        }
    }

    private async writeReportFile(): Promise<void> {
        if (!toolchain.globalOption.reportFile) {
            console.log('no report file path!');
            return;
        }
        const sum: IReportDoc = {
            noTranslators: this.noTranslators,
            concatStrErrors: this.concatStrErrors,
            jsonSafeErrors: this.jsonSafeErrors,
            langs: this.langMap
        };
        await fs.writeJson(toolchain.globalOption.reportFile, sum, { spaces: 2 });
    }

    public async printMultiLines(group: string, arr: string[]): Promise<void> {
        const startLine = `--------------${group} start--------------`, endLine = `---------------${group} end---------------`;
        console.error(startLine);
        const maxlen = toolchain.globalOption.debug ? 5 : 25;
        const len = Math.min(arr.length, maxlen);
        for (let i = 0; i < len; i++) {
            console.error(arr[i]);
        }
        if (arr.length > len) {
            console.log(`              ${arr.length - len} more...`);
        }
        console.error(endLine);

        if (toolchain.globalOption.logFile) {
            await fs.appendFile(toolchain.globalOption.logFile, startLine + '\n' + arr.join('\n') + '\n' + endLine + '\n', 'utf-8');
        }
    }

    private touchReport(lang: TLangs): IReport {
        let report = this.langMap[lang];
        if (report == null) {
            this.langMap[lang] = report = {
                noLocals: [],
                missedFmts: [],
                fmtErrors: [],
                termCNs: [],
                termENs: []
            };
        }
        return report;
    }
}
